module SpiceInit
  module Instance
    def insinit(*args,&block)
      # get space for a new object of class‘s class and extract options
      object, spices = allocate, args.extract_options!

      # are we tracking spices?
      track_spices = spices.has_key?(:track_spices) ? spices.delete(:track_spices) : false

      # get the objet's meta class and add spices
      (class << object;self;end).class_eval do
        # should we keep track of spices added?
        if track_spices
          attr_reader :spices_added
          object.instance_variable_set("@spices_added",[])
        end

        # loop through all spices
        spices.each do |key,value|
          # first check if we have a punctutated method key
          match = key.to_s.match(/([\!\?]$)/)
          if match
            # if we are here, method key is punctuated
            # writer and variable name are everything but the punctuation mark
            setter = var = match.pre_match
            # the reader equals the writer with punctuation mark
            getter = setter + match[1]
            # we need a reader
            define_method("#{getter}") do
              instance_variable_get("@#{var}")
            end
            # we need a writer
            attr_writer setter
            # ready to set the instance variable with assigned value
            object.instance_variable_set("@#{var}", value)
            # track these spices if required
            object.spices_added << "#{getter}" << "#{setter}=" if track_spices
          else
            # if we are here, method key is not punctuated
            # key is the same for reader, writer, and variable name
            attr_accessor key
            # ready to set the instance variable with assigned value
            object.instance_variable_set("@#{key}", value)
            # track these spices if required
            object.spices_added << "#{key}" << "#{key}=" if track_spices
          end
        end
      end

      # call initialize but set the block to nil to prevent multiple yields
      object.send(:initialize, *args, &block=nil)

      # if a block is given then pass the object for more processing
      yield(object) if block_given?

      # return the final object
      object
    end

    alias_method :inspice, :insinit
  end
end